package server.loader;

import server.mapper.Host;

import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Created by louisyuu on 2020/4/1 下午4:39
 */
public class WebAppsClassLoader extends ClassLoader {

    private final String SLASH = "/";

    private final String WEB_INF_PATH = "/WEB-INF";

    private final String CLASSES_PATH = "/classes";

    private final String LIB_PATH = "/lib";


    /**
     * /webapps/web-demo-one
     */
    private String webAppsClassPath;


    /**
     * /webapps/web-demo-one//WEB-INF
     */
    private String webAppsWebInfPath;


    /**
     * /webapps/web-demo-one//WEB-INF/classes
     */
    private String webAppsClassesPath;

    /**
     * /webapps/web-demo-one//WEB-INF/lib
     */
    private String webAppsLibPath;

    /**
     * /Users/lewis/JavaWorkspace/study/Secondary/tomcat/mine/my-minicat-demo/minicat/src/main/resources/webapps
     */
    private String webAppsAbsolutePath;

    /**
     * /Users/lewis/JavaWorkspace/study/Secondary/tomcat/mine/my-minicat-demo/minicat/src/main/resources/webapps/classes
     */
    private String webAppsAbsoluteClassesPath;

    /**
     * /Users/lewis/JavaWorkspace/study/Secondary/tomcat/mine/my-minicat-demo/minicat/src/main/resources/webapps/lib
     */
    private String webAppsAbsoluteLibPath;

    private Host host;

    private String docBase;


    public WebAppsClassLoader(Host host, String docBase) {
        this.host = host;
        this.docBase = docBase;

        /**
         * webapps/web-demo-one
         */
        this.webAppsClassPath = host.getAppBase() + docBase;


        this.webAppsWebInfPath = webAppsClassPath + WEB_INF_PATH;

        /**
         * /webapps/web-demo-one//WEB-INF/classes
         */
        this.webAppsClassesPath = SLASH + webAppsClassPath + WEB_INF_PATH + CLASSES_PATH;

        /**
         * /webapps/web-demo-one//WEB-INF/lib
         */
        this.webAppsLibPath = SLASH + webAppsClassPath + WEB_INF_PATH + LIB_PATH;

        /**
         * /Users/lewis/JavaWorkspace/study/Secondary/tomcat/mine/my-minicat-demo/minicat/src/main/resources/webapps
         */
        this.webAppsAbsolutePath = host.getAppBaseAbsolutePath();

        /**
         * /Users/lewis/JavaWorkspace/study/Secondary/tomcat/mine/my-minicat-demo/minicat/src/main/resources/webapps/web-demo-one//WEB-INF/classes
         */
        this.webAppsAbsoluteClassesPath = this.webAppsAbsolutePath + webAppsClassesPath.replace(host.getAppBase(), "");


        /**
         * /Users/lewis/JavaWorkspace/study/Secondary/tomcat/mine/my-minicat-demo/minicat/src/main/resources/webapps/web-demo-one//WEB-INF/lib
         */
        this.webAppsAbsoluteLibPath = this.webAppsAbsolutePath + webAppsLibPath.replace(host.getAppBase(), "");


    }

    /**
     * 破坏了双亲委派模型
     *
     * @param name 全限定类名
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {

            /**
             * demo.servlet.DemoServlet
             * convert to
             * demo/servlet/DemoServlet.class
             */
            String loadedClassPath = name.replace(".", "/") + ".class";

            String webAppClassResourcePath = this.webAppsClassesPath + SLASH + loadedClassPath;


            /**
             * 1.优先从/webapps/web-demo-one//WEB-INF/classes classPath路径加载
             */
            InputStream is = getClass().getResourceAsStream(webAppClassResourcePath);
            if (is == null) {
                /**
                 * 2.如果加载不到在/webapps/web-demo-one//WEB-INF/lib 第三方jar包中加载
                 */


                Class clazz = loadClassFromJarFile(this.webAppsAbsoluteLibPath, name);
                if (clazz != null) {
                    return clazz;
                }
                /**
                 * 3.去BootStrap class loader加载
                 */
                return super.loadClass(name);
            }
            byte[] b = new byte[is.available()];
            is.read(b);
            return defineClass(name, b, 0, b.length);
        } catch (Exception e) {
            throw new ClassNotFoundException(name);
        }
    }


    /**
     * Uses Parents Delegation Model
     * 遵从了双亲委派模型
     *
     * @param name 全限定类名
     * @return
     * @throws ClassNotFoundException
     */
    public Class<?> loadClassUseParentsDelegationModel(String name) throws ClassNotFoundException {
        try {
            /**
             * 1.去Parents class loader加载
             */
            return super.loadClass(name);
        } catch (ClassNotFoundException e) {

            /**
             * 父类加载不到去子类的类加载器加载
             */
            return loadClass(name);
        }
    }


    /**
     * /Users/lewis/JavaWorkspace/study/Secondary/tomcat/mine/my-minicat-demo/minicat/src/main/resources/webapps/web-demo-one/WEB-INF/lib
     *
     * @param path absolutePath
     * @param name 全限定类名
     * @throws Exception
     */
    public Class loadClassFromJarFile(String path, String name) throws Exception {
        //系统类库路径

        File libPath = new File(path);

        // 获取所有的.jar和.zip文件
        File[] jarFiles = libPath.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar") || name.endsWith(".zip");
            }
        });

        if (jarFiles != null) {
            // 从URLClassLoader类中获取类所在文件夹的方法
            // 对于jar文件，可以理解为一个存放class文件的文件夹
            Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            boolean accessible = method.isAccessible();        // 获取方法的访问权限
            try {
                if (accessible == false) {
                    method.setAccessible(true);        // 设置方法的访问权限
                }
                // 获取系统类加载器
                URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
                for (File file : jarFiles) {
                    URL url = file.toURI().toURL();
                    try {
                        method.invoke(classLoader, url);

                        return classLoader.loadClass(name);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                method.setAccessible(accessible);
            }
        }
        return null;
    }


    public String getWebAppsClassesPath() {
        return webAppsClassesPath;
    }

    public String getWebAppsLibPath() {
        return webAppsLibPath;
    }

    public String getWebAppsAbsolutePath() {
        return webAppsAbsolutePath;
    }

    public String getWebAppsAbsoluteClassesPath() {
        return webAppsAbsoluteClassesPath;
    }

    public String getWebAppsAbsoluteLibPath() {
        return webAppsAbsoluteLibPath;
    }


    public String getWebAppsClassPath() {
        return webAppsClassPath;
    }


    public String getWebAppsWebInfPath() {
        return webAppsWebInfPath;
    }
}
